Skip to content

Add sticky user message overlay#2703

Open
LukasFB wants to merge 1 commit into
pingdotgg:mainfrom
LukasFB:feature/sticky-user-messages
Open

Add sticky user message overlay#2703
LukasFB wants to merge 1 commit into
pingdotgg:mainfrom
LukasFB:feature/sticky-user-messages

Conversation

@LukasFB
Copy link
Copy Markdown

@LukasFB LukasFB commented May 14, 2026

What Changed

Added configurable, sticky user messages in the messages timeline

Why

Switching between multiple sessions I find that to be a great help for quickly grasping where the conversation left off. Click-to-scroll also doubles as a quick way to jump to the beginning of the agent turn in case the user wants to skim it.

UI Changes

This changes the chat timeline UI.

Before: user messages scroll out of view during long assistant responses.
Screenshot 2026-05-14 at 21 19 34

After: recent user prompts remain visible as compact sticky bubbles while scrolling. The newest sticky bubble includes the timestamp. Clicking the bubble scrolls the view to the original position
Screenshot 2026-05-14 at 21 19 58

Demo video:
https://github.com/user-attachments/assets/d92d0b37-da5a-4bd5-bbce-93e457b8cc49

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Medium risk because it introduces new persisted client settings and non-trivial scroll/viewport measurement logic that could affect timeline rendering and scrolling behavior.

Overview
Adds a sticky overlay in MessagesTimeline that can pin the last 1–2 user prompts as compact bubbles once their original rows scroll above the viewport, with click-to-scroll back to the source message.

Introduces new client settings (stickyUserMessageCount, stickyUserMessageMaxLines) with bounded schema defaults, wires them from ChatView into the timeline, and exposes controls plus reset/dirty-state handling in Settings.

Updates desktop/web/local API/settings contract tests and adds browser-focused timeline tests to validate sticky visibility rules, metadata display, and scroll-to-source behavior.

Reviewed by Cursor Bugbot for commit 47ea97d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add sticky user message overlay to chat transcript

  • Adds a StickyUserMessagesOverlay that pins recent user messages to the top of the chat viewport when their source rows scroll above the visible area, with click-to-scroll navigation back to the original message.
  • Introduces StickyUserMessageBubble which clamps message text to a configurable max lines and shows a +N lines indicator and timestamp on the newest visible bubble.
  • Adds stickyUserMessageCount (0–2, default 0) and stickyUserMessageMaxLines (1–3, default 2) to client settings with schema validation, defaults, and a settings UI in the General panel.
  • The overlay is driven by useHiddenStickyUserMessageIds, which attaches scroll/resize listeners and uses requestAnimationFrame to batch DOM measurements of source element positions.
📊 Macroscope summarized 47ea97d. 6 files reviewed, 4 issues evaluated, 1 issue filtered, 1 comment posted

🗂️ Filtered Issues

apps/web/src/components/chat/MessagesTimeline.browser.tsx — 0 comments posted, 2 evaluated, 1 filtered
  • line 253: The mock LegendList component (lines 16-42) does not forward the onScroll prop to the rendered <div>. When the test at line 253 dispatches new Event("scroll") on [data-testid='legend-list'], the component's onScroll handler passed to LegendList is never invoked. This means the scroll-triggered visibility logic in useHiddenStickyUserMessageIds may not execute, causing the test "shows a sticky user message only after its source row is above the transcript viewport" to potentially pass or fail for reasons unrelated to the actual scroll behavior. [ Failed validation ]

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cb432cb8-ccb7-4fb8-bfbb-9d3078364901

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels May 14, 2026
const listState = listRef.current?.getState?.();
const currentEntries = entriesRef.current;

setHiddenMessageIds((current) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium chat/StickyUserMessagesOverlay.tsx:97

Equal.equals(current, next) compares two plain JavaScript Set objects using Effect's Equal.equals. Plain Set instances don't implement Effect's Equal interface, so the comparison falls back to reference equality (===), which always returns false for distinct Set instances. This causes setHiddenMessageIds to always return a new Set on every scroll/resize measurement, triggering unnecessary re-renders even when the hidden IDs haven't changed.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/chat/StickyUserMessagesOverlay.tsx around line 97:

`Equal.equals(current, next)` compares two plain JavaScript `Set` objects using Effect's `Equal.equals`. Plain `Set` instances don't implement Effect's `Equal` interface, so the comparison falls back to reference equality (`===`), which always returns `false` for distinct `Set` instances. This causes `setHiddenMessageIds` to always return a new `Set` on every scroll/resize measurement, triggering unnecessary re-renders even when the hidden IDs haven't changed.

Evidence trail:
1. `apps/web/src/components/chat/StickyUserMessagesOverlay.tsx` lines 97-119 (REVIEWED_COMMIT) — `setHiddenMessageIds` callback creates `new Set<MessageId>()` and uses `Equal.equals(current, next)` to avoid returning a new reference.
2. Effect library `packages/effect/src/Equal.ts` lines 38-88 (https://github.com/Effect-TS/effect) — `compareBoth` function: for plain objects without `Equal` symbol, falls through to check `structuralRegionState.enabled`; for non-Array/non-plain-Object types like Set, returns false.
3. `packages/effect/src/Utils.ts` lines 747-752 (Effect-TS/effect) — `structuralRegionState` defaults to `{ enabled: false, tester: undefined }`.
4. `git_grep` for `structuralRegion` in pingdotgg/t3code — zero results, confirming structural region is never enabled.
5. Line 8 of the file: `import * as Equal from "effect/Equal";` confirms it's the Effect library's Equal module.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this against the installed Effect version in this repo (effect@4.0.0-beta.59) and native Set instances are handled structurally, not by reference.

Equal.ts has an explicit self instanceof Set branch which delegates to compareSets.
Runtime check confirms:
Equal.equals(new Set(["a", "b"]), new Set(["b", "a"])) === true

That said, I’m happy to switch this to a local explicit MessageId set comparison helper if maintainers prefer that for readability in the scroll path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 14, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new user-facing feature (sticky user message overlay) with substantial new logic including a 356-line component, new settings, and scroll tracking behavior. Additionally, an unresolved comment identifies a potential performance issue with unnecessary re-renders. New feature additions of this scope warrant human review.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant